﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using FreeImageAPI;
using iTextSharp.text.pdf;
using PDFPatcher.Common;
using PDFPatcher.Model;
using PDFPatcher.Processor.Imaging;

namespace PDFPatcher.Processor
{
	sealed class ImageExtractor
	{
		readonly PdfPageImageProcessor _parser;
		int _totalImageCount;
		int _imageCount;
		int _activePage; // 在导出文件图片时，使用此属性命名文件
		readonly string _fileMask;
		int _pageRotation;
		readonly ImageExtracterOptions _options;
		readonly List<ImageInfo> _imageInfoList = new List<ImageInfo>();
		readonly List<ImageDisposition> _imagePosList = new List<ImageDisposition>();
		readonly HashSet<PdfObject> _exportedImages = new HashSet<PdfObject>();
		readonly Stack<PdfObject> _visited = new Stack<PdfObject>();

		internal List<ImageInfo> InfoList => _imageInfoList;
		internal List<ImageDisposition> PosList => _imagePosList;
		internal bool PrintImageLocation { get; set; }

		public ImageExtractor(ImageExtracterOptions options) {
			_fileMask = String.IsNullOrEmpty(options.FileMask) ? "0" : options.FileMask;
			_options = options;
			_parser = new PdfPageImageProcessor(_imagePosList, _imageInfoList);
		}

		internal void ExtractPageImages(PdfReader reader, int pageNum) {
			if (pageNum < 1 || pageNum > reader.NumberOfPages) {
				return;
			}
			_activePage = pageNum;
			_parser.Reset();
			_imageCount = 0;
			_imageInfoList.Clear();
			_imagePosList.Clear();
			_visited.Clear();
			var o = reader.GetPageNRelease(pageNum);
			if (o == null) {
				return;
			}

			// 收集页面字典引用的图片
			var pp = o.Locate<PdfDictionary>(PdfName.RESOURCES, PdfName.XOBJECT);
			if (pp != null) {
				ExtractImageInstances(pp, false);
				// 收集页面指令引用的图片
				_parser.ProcessContent(reader.GetPageContent(pageNum), o.Locate<PdfDictionary>(PdfName.RESOURCES));
				_imagePosList.Sort();

				// 删除页面字典中没有在渲染指令中出现的图片
				if (_options.ExtractOutOfPageImages == false) {
					_imageInfoList.RemoveAll(IsOutOfPage);
				}
			}
			// 收集批注中的图片
			if (_options.ExtractAnnotationImages) {
				var an = o.Locate<PdfArray>(PdfName.ANNOTS);
				if (an != null) {
					foreach (var item in an.ArrayList) {
						ExtractImageInstances(PdfReader.GetPdfObjectRelease(item) as PdfDictionary, true);
					}
				}
			}
			if (_imageInfoList.Count == 0) {
				return;
			}
			_pageRotation = PdfHelper.GetPageRotation(o);

			_imageInfoList.Sort((x, y) => {
				var xi = _imagePosList.Find((info) => info.Image == x);
				var yi = _imagePosList.Find((info) => info.Image == y);
				if (xi == null) {
					return yi == null ? 0 : -1;
				}
				else if (yi == null) {
					return -1;
				}
				return xi.CompareTo(yi);
			});
			foreach (var item in _imageInfoList) {
				try {
					ExtractImage(item);
				}
				catch (FreeImageException ex) {
					if (item.ReferenceCount > 0) {
						Tracker.TraceMessage(Tracker.Category.Error, "在导出第 " + pageNum + " 页图片时遇到错误：" + ex.Message);
					}
				}
			}
			if (_options.MergeImages && _imagePosList.Count > 1) {
				// 合并相同宽度、相同类型的图片
				MergeImages();
			}
		}

		bool IsOutOfPage(ImageInfo image) {
			if (image.IsPageImage == false) {
				return false;
			}
			var r = image.InlineImage.PdfRef;
			foreach (var item in _imagePosList) {
				var ir = item.Image.InlineImage?.PdfRef;
				if (ir == null || (ir.Number == r.Number && ir.Generation == r.Generation)) {
					return false;
				}
			}
			return true;
		}

		void ExtractImageInstances(PdfDictionary source, bool recursive) {
			if (source == null) {
				return;
			}
			foreach (var item in source) {
				if (PdfName.SMASK.Equals(item.Key)
					|| PdfName.MASK.Equals(item.Key)
					|| _options.AllowRedundantImages == false && _exportedImages.Contains(item.Value)
					|| PdfReader.GetPdfObject(item.Value) is not PdfDictionary obj
					|| _visited.Contains(item.Value)) {
					continue;
				}
				_visited.Push(item.Value);
				if (obj is not PRStream stream) {
					goto NEXT;
				}
				PdfName subType = stream.GetAsName(PdfName.SUBTYPE);
				if (PdfName.IMAGE.Equals(subType)) {
					try {
						_imageInfoList.Add(new ImageInfo(item.Value as PRIndirectReference, !recursive));
					}
					catch (NullReferenceException) {
						Debug.WriteLine(item.Value);
					}
					continue;
				}
				if (PdfName.FORM.Equals(subType)) {
					var fr = stream.Locate<PdfDictionary>(PdfName.RESOURCES, PdfName.XOBJECT);
					if (fr == null) {
						continue;
					}
					foreach (var fri in fr) {
						if (_exportedImages.Contains(fri.Value)) {
							continue;
						}
						stream = PdfReader.GetPdfObject(fri.Value) as PRStream;
						if (stream != null) {
							subType = stream.GetAsName(PdfName.SUBTYPE);
							if (PdfName.IMAGE.Equals(subType)) {
								_imageInfoList.Add(new ImageInfo(fri.Value as PRIndirectReference, false));
							}
							else if (recursive || PdfName.FORM.Equals(subType)) {
								ExtractImageInstances(stream, true);
							}
						}
						else if (recursive) {
							ExtractImageInstances(stream, true);
						}
					}
				}
			NEXT:
				if (recursive) {
					ExtractImageInstances(obj, true);
				}
				_visited.Pop();
			}
		}

		internal void ExtractImage(ImageInfo info) {
			if (_options.AllowRedundantImages == false
				&& _exportedImages.Add(info.InlineImage.PdfRef) == false) {
				return;
			}
			if (_totalImageCount == 0 && Directory.Exists(_options.OutputPath) == false) {
				Directory.CreateDirectory(_options.OutputPath);
			}
			var bytes = info.DecodeImage(_options);
			if (bytes == null) {
				return;
			}
			if (info.LastDecodeError != null) {
				Tracker.TraceMessage(Tracker.Category.Error, info.LastDecodeError);
				return;
			}
			var fileName = GetNewImageFileName();
			if (info.ExtName == Constants.FileExtensions.Png
				|| info.ExtName == Constants.FileExtensions.Tif
				) {
				SaveBitmap(info, bytes, fileName);
			}
			else {
				SaveImageBytes(info, bytes, fileName);
			}
			if (info.Mask != null) {
				using (var m = info.Mask) {
					m.Palette.CreateGrayscalePalette();
					m.Save($"{fileName}[mask]{(info.Mask.PixelFormat == PixelFormat.Format1bppIndexed ? Constants.FileExtensions.Tif : Constants.FileExtensions.Png)}");
				}
			}
			_totalImageCount++;
		}

		void SaveImageBytes(ImageInfo info, byte[] bytes, string fileName) {
			var vFlip = _options.VerticalFlipImages ^ info.VerticalFlip;
			var n = fileName + info.ExtName;
			if (PrintImageLocation) {
				Tracker.TraceMessage(Tracker.Category.OutputFile, n);
				Tracker.TraceMessage("导出图片：" + n);
			}
			if (info.ExtName == Constants.FileExtensions.Jp2) {
				if (vFlip || _pageRotation != 0) {
					try {
						using (var ms = new MemoryStream(bytes))
						using (var bmp = new FreeImageBitmap(ms)) {
							RotateBitmap(bmp, _pageRotation, vFlip);
							info.CreatePaletteAndIccProfile(bmp);
							try {
								bmp.Save(n);
							}
							catch (FreeImageException) {
								File.Delete(n);
								bmp.Save(new FilePath(n).ChangeExtension(Constants.FileExtensions.Png));
							}
							SaveMaskedImage(info, bmp, fileName);
						}
					}
					catch (FreeImageException ex) {
						Tracker.TraceMessage(ex);
						bytes.DumpBytes(n);
					}
				}
				else {
					using (FileStream f = new FileStream(n, FileMode.Create)) {
						f.Write(bytes, 0, bytes.Length);
					}
				}
			}
			else if (PdfName.DEVICECMYK.Equals(info.ColorSpace)) {
				using (var ms = new MemoryStream(bytes))
				using (var bmp = new FreeImageBitmap(ms, FREE_IMAGE_LOAD_FLAGS.JPEG_CMYK | FREE_IMAGE_LOAD_FLAGS.TIFF_CMYK)) {
					RotateBitmap(bmp, _pageRotation, vFlip);
					if (bmp.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_24_BPP)) {
						SwapRedBlue(bmp);
						n = fileName + Constants.FileExtensions.Png;
						bmp.Save(n, FREE_IMAGE_FORMAT.FIF_PNG);
					}
					else {
						n = fileName + Constants.FileExtensions.Tif;
						bmp.Save(n, FREE_IMAGE_FORMAT.FIF_TIFF, FREE_IMAGE_SAVE_FLAGS.TIFF_CMYK | FREE_IMAGE_SAVE_FLAGS.TIFF_DEFLATE);
					}
					SaveMaskedImage(info, bmp, fileName);
					if (PrintImageLocation) {
						Tracker.TraceMessage("导出图片：" + n);
					}
				}
			}
			else {
				using (FileStream f = new FileStream(n, FileMode.Create)) {
					f.Write(bytes, 0, bytes.Length);
				}
				if (info.ExtName == Constants.FileExtensions.Jpg) {
					if (info.Mask != null) {
						using (var bmp = new FreeImageBitmap(new MemoryStream(bytes), FREE_IMAGE_FORMAT.FIF_JPEG)) {
							SaveMaskedImage(info, bmp, fileName);
						}
					}
					if (vFlip) {
						TransformJpeg(n, FREE_IMAGE_JPEG_OPERATION.FIJPEG_OP_FLIP_V);
					}
					if (_pageRotation != 0) {
						TransformJpeg(n,
							_pageRotation == 90 ? FREE_IMAGE_JPEG_OPERATION.FIJPEG_OP_ROTATE_90
							: _pageRotation == 180 ? FREE_IMAGE_JPEG_OPERATION.FIJPEG_OP_ROTATE_180
							: _pageRotation == 270 ? FREE_IMAGE_JPEG_OPERATION.FIJPEG_OP_ROTATE_270
							: FREE_IMAGE_JPEG_OPERATION.FIJPEG_OP_NONE);
					}
				}
			}
			info.FileName = n;
		}

		static void SwapRedBlue(FreeImageBitmap bmp) {
			var r = bmp.GetChannel(FREE_IMAGE_COLOR_CHANNEL.FICC_RED);
			var b = bmp.GetChannel(FREE_IMAGE_COLOR_CHANNEL.FICC_BLUE);
			bmp.SetChannel(b, FREE_IMAGE_COLOR_CHANNEL.FICC_RED);
			bmp.SetChannel(r, FREE_IMAGE_COLOR_CHANNEL.FICC_BLUE);
		}

		static void TransformJpeg(string fileName, FREE_IMAGE_JPEG_OPERATION operation) {
			var tmpName = fileName + Constants.FileExtensions.Tmp;
			if (FreeImageBitmap.JPEGTransform(fileName, tmpName, operation, true)) {
				File.Delete(fileName);
				File.Move(tmpName, fileName);
				return;
			}
			File.Delete(tmpName);
			RotateFlipType type;
			switch (operation) {
				case FREE_IMAGE_JPEG_OPERATION.FIJPEG_OP_FLIP_H:
					type = RotateFlipType.RotateNoneFlipX;
					break;
				case FREE_IMAGE_JPEG_OPERATION.FIJPEG_OP_FLIP_V:
					type = RotateFlipType.RotateNoneFlipY;
					break;
				case FREE_IMAGE_JPEG_OPERATION.FIJPEG_OP_ROTATE_90:
					type = RotateFlipType.Rotate270FlipNone;
					break;
				case FREE_IMAGE_JPEG_OPERATION.FIJPEG_OP_ROTATE_180:
					type = RotateFlipType.Rotate180FlipNone;
					break;
				case FREE_IMAGE_JPEG_OPERATION.FIJPEG_OP_ROTATE_270:
					type = RotateFlipType.Rotate90FlipNone;
					break;
				default:
					Tracker.TraceMessage(Tracker.Category.Error, "无损翻转 JPG 图片失败：" + fileName);
					return;
			}
			using (var bmp = new FreeImageBitmap(fileName)) {
				bmp.RotateFlip(type);
				if (bmp.UniqueColors < 256) {
					bmp.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_08_BPP);
				}
				bmp.Save(new FilePath(fileName).ChangeExtension(Constants.FileExtensions.Png));
			}
			File.Delete(fileName);
		}

		void SaveBitmap(ImageInfo info, byte[] bytes, string fileName) {
			var vFlip = _options.VerticalFlipImages ^ info.VerticalFlip;
			var ext = info.ExtName;
			if (info.PixelFormat == PixelFormat.Format1bppIndexed) {
				ext = _options.MonoPng == false ? Constants.FileExtensions.Tif : Constants.FileExtensions.Png;
			}
			var n = fileName + ext;
			if (PrintImageLocation) {
				Tracker.TraceMessage(Tracker.Category.OutputFile, n);
				Tracker.TraceMessage("导出图片：" + n);
			}
			if (PdfName.DEVICECMYK.Equals(info.ColorSpace)) {
				// TODO: 转换字节数组的 CMYK 为 RGB 后加载到 FreeImageBitmap
				//info.PixelFormat = PixelFormat.Undefined;
				using (var bmp = new FreeImageBitmap(
					//info.Width,
					//info.Height,
					//GetStride (info, bytes, vFlip),
					//PixelFormat.Format32bppArgb, bytes
					new MemoryStream(bytes), FREE_IMAGE_LOAD_FLAGS.JPEG_CMYK
					)) {
					if (info.ICCProfile != null) {
						bmp.CreateICCProfile(info.ICCProfile);
					}
					RotateBitmap(bmp, _pageRotation, false);
					if (bmp.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_24_BPP)) {
						SwapRedBlue(bmp);
						n = fileName + Constants.FileExtensions.Png;
						bmp.Save(n, FREE_IMAGE_FORMAT.FIF_PNG);
					}
					else {
						bmp.Save(n,
							FREE_IMAGE_FORMAT.FIF_TIFF,
							FREE_IMAGE_SAVE_FLAGS.TIFF_CMYK | FREE_IMAGE_SAVE_FLAGS.TIFF_DEFLATE);
					}
					SaveMaskedImage(info, bmp, fileName);
				}
			}
			else {
				using (var bmp = CreateFreeImageBitmap(info, ref bytes, vFlip, true)) {
					if (ext == Constants.FileExtensions.Png
							&& _options.InvertBlackAndWhiteImages
							&& info.PixelFormat == PixelFormat.Format1bppIndexed
							&& bmp.Palette.Length == 2) {
						bmp.SwapPaletteIndices(0, 1);
					}
					RotateBitmap(bmp, _pageRotation, false);
					if (ext == Constants.FileExtensions.Tif) {
						TiffHelper.Save(bmp, n);
					}
					else {
						try {
							bmp.Save(n, FREE_IMAGE_FORMAT.FIF_PNG);
						}
						catch (FreeImageException ex) when (ex.Message == "Unable to save bitmap") {
							using (var b = bmp.ToBitmap()) {
								b.Save(n, ImageFormat.Png);
							}
						}
						catch (System.Runtime.InteropServices.SEHException) {
							Tracker.TraceMessage(Tracker.Category.Error, "保存图片时出现错误，请联系程序开发者：" + n);
						}
						SaveMaskedImage(info, bmp, fileName);
					}
				}
			}
			info.FileName = n;
		}

		static void RotateBitmap(FreeImageBitmap bitmap, int rotation, bool vflip) {
			if (rotation == 0 && vflip == false) {
				return;
			}
			RotateFlipType r;
			switch (rotation) {
				case 0: r = RotateFlipType.RotateNoneFlipY; break;
				case 90: r = vflip ? RotateFlipType.Rotate270FlipY : RotateFlipType.Rotate270FlipNone; break;
				case 180: r = vflip ? RotateFlipType.Rotate180FlipY : RotateFlipType.Rotate180FlipNone; break;
				case 270: r = vflip ? RotateFlipType.Rotate90FlipY : RotateFlipType.Rotate90FlipNone; break;
				default: return;
			}
			bitmap.RotateFlip(r);
		}

		internal static FreeImageBitmap CreateFreeImageBitmap(ImageInfo info, ref byte[] bytes, bool vFlip, bool loadPaletteAndIccp) {
			if (info.ExtName != Constants.FileExtensions.Jpg && info.ExtName != Constants.FileExtensions.Jp2) {
				info.ConvertDecodedBytes(ref bytes);
			}
			FreeImageBitmap bmp;
			if (PdfName.DEVICECMYK.Equals(info.ColorSpace)) {
				bmp = new FreeImageBitmap(new MemoryStream(bytes), FREE_IMAGE_LOAD_FLAGS.TIFF_CMYK);
			}
			else if (info.ExtName == Constants.FileExtensions.Jp2 || info.ExtName == Constants.FileExtensions.Jpg) {
				bmp = new FreeImageBitmap(new MemoryStream(bytes));
			}
			else {
				bmp = new FreeImageBitmap(info.Width, info.Height, GetStride(info, bytes, vFlip), info.PixelFormat, bytes);
			}
			if (loadPaletteAndIccp
				|| info.ColorSpace == PdfName.DEVICEGRAY && info.PaletteColorSpace == null) {
				info.CreatePaletteAndIccProfile(bmp);
			}
			return bmp;
		}

		static int GetStride(ImageInfo info, byte[] bytes, bool vFlip) {
			if (PdfName.COLORSPACE.Equals(info.ColorSpace)) {
				return vFlip ? -(info.Width << 2) : (info.Width << 2);
			}
			var components = bytes.Length / info.Width / info.Height;
			var stride = components > 0
				? info.Width * components
				: (info.Width + 8 / info.BitsPerComponent - 1) / (8 / info.BitsPerComponent);
			return vFlip ? -stride : stride;
		}

		string GetNewImageFileName() {
			_imageCount++;
			return String.Concat(
				FileHelper.CombinePath(_options.OutputPath, _activePage.ToString(_fileMask)),
				_imageCount > 1 ? "[" + _imageCount + "]" : null);
		}

		void MergeImages() {
			var l = _imagePosList.Count;
			for (int i = 0; i < l; i++) {
				var imageI = _imagePosList[i];
				// 由于在导出图像时仅为 PNG 和 TIF 指定 ImageInfo 的 PixelFormat，因此合并过程中仅处理这两类文件
				if (imageI.Image.ReferenceCount < 1 // 图像已处理
					|| imageI.Image.PixelFormat == PixelFormat.Undefined // 不属于可合并的类型
					|| l - i < 2 // 是最后一张图片
					) {
					continue;
				}
				var imageParts = new ImageInfo[l - i];
				var w = imageI.Image.Width;
				var h = 0;
				var i2 = 0;
				for (int j = i; j < l; j++) {
					var imageJ = _imagePosList[j];
					if (imageJ.Image.ReferenceCount < 1 // 图像已处理
						|| imageJ.Image.Width != w // 宽度不相符
						|| Math.Abs(Math.Round(imageJ.X - imageI.X)) > 1 // 位置相差超过 1 点
						) {
						continue;
					}
					imageParts[i2] = imageJ.Image;
					h += imageJ.Image.Height;
					_imagePosList[j].Image.ReferenceCount--; // 避免重复处理
					i2++;
				}
				if (i2 == 0) { // 没有符合合并条件的图片
					continue;
				}
				if (i2 == 1) {
					_imagePosList[i].Image.ReferenceCount++;
					continue;
				}
				if (i2 < imageParts.Length) {
					Array.Resize(ref imageParts, i2);
				}
				if (PrintImageLocation) {
					Tracker.TraceMessage("合并图片：" + String.Join("、", Array.ConvertAll<ImageInfo, string>(imageParts, p => Path.GetFileName(p.FileName))));
				}
				var ext = Path.GetExtension(imageI.Image.FileName).ToLowerInvariant();
				if (imageI.Image.PixelFormat == PixelFormat.Format1bppIndexed) {
					ext = Constants.FileExtensions.Tif;
				}
				var f = GetNewImageFileName();
				using (FreeImageBitmap bmp = new FreeImageBitmap(w, h, imageI.Image.PixelFormat)) {
					var mi = new ImageMerger(bmp);
					foreach (var part in imageParts) {
						using (var bmp2 = FreeImageBitmap.FromFile(part.FileName)) {
							mi.MergeImage(bmp2);
						}
					}
					mi.TryReduceColor();
					if (ext == Constants.FileExtensions.Jpg && _options.MergeJpgToPng) {
						ext = Constants.FileExtensions.Png;
					}
					else if (bmp.PixelFormat == PixelFormat.Format1bppIndexed) {
						if (_options.MonoPng == false) {
							ext = Constants.FileExtensions.Tif;
						}
						else {
							ext = Constants.FileExtensions.Png;
						}
					}
					f += ext;
					if (PrintImageLocation) {
						Tracker.TraceMessage(Tracker.Category.OutputFile, f);
						Tracker.TraceMessage("保存合并后的图片：" + f);
					}
					if (ext == Constants.FileExtensions.Tif) {
						TiffHelper.Save(bmp, f);
					}
					else {
						bmp.Save(f);
					}
					var mii = new ImageInfo { FileName = f, ReferenceCount = 1, Height = h, Width = w };
					_imageInfoList.Add(mii);
					_imagePosList.Add(new ImageDisposition(_imagePosList[i].Ctm, mii));
				}
			}
			DeleteIntermediateFiles();
			_imageInfoList.Sort((ImageInfo x, ImageInfo y) => string.Compare(x.FileName, y.FileName, StringComparison.OrdinalIgnoreCase));
			_totalImageCount -= _imageCount;
			_imageCount = 0;
			var newFileNames = new List<string>();
			foreach (var item in _imageInfoList) {
				if (item.FileName != null && item.InlineImage == null) {
					string n;
					do {
						n = GetNewImageFileName() + Path.GetExtension(item.FileName);
					} while (_imagePosList.Exists((i) => i.Image.FileName == n) || newFileNames.Contains(n));
					if (PrintImageLocation) {
						Tracker.TraceMessage(String.Concat("重命名合并后的文件 ", item.FileName, " 为 ", n));
						Tracker.TraceMessage(Tracker.Category.OutputFile, n);
					}
					newFileNames.Add(n);
					File.Delete(n);
					File.Move(item.FileName, n);
					item.FileName = n;
				}
			}
			_totalImageCount += _imageCount;
		}

		void DeleteIntermediateFiles() {
			foreach (var item in _imageInfoList) {
				if (item.ReferenceCount < 1) {
					File.Delete(item.FileName);
					item.FileName = null;
				}
			}
		}

		static void SaveMaskedImage(ImageInfo info, FreeImageBitmap bmp, string fileName) {
			if (info.Mask != null
				&& info.Mask.Size == bmp.Size
				&& (bmp.PixelFormat == PixelFormat.Format32bppArgb
					|| bmp.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_32_BPP))) {
				var m = (FreeImageBitmap)info.Mask.Clone();
				if ((m.PixelFormat == PixelFormat.Format8bppIndexed || m.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_08_BPP | FREE_IMAGE_COLOR_DEPTH.FICD_FORCE_GREYSCALE))
					&& bmp.SetChannel(m, FREE_IMAGE_COLOR_CHANNEL.FICC_ALPHA)) {
					bmp.Save($"{fileName}[merged]{Constants.FileExtensions.Png}");
				}
			}
		}

		sealed class ImageMerger
		{
			readonly FreeImageBitmap _Image;
			ColorType _ColorType;
			RGBQUAD[] _Colors;
			int _Height;

			public ImageMerger(FreeImageBitmap image) {
				_Image = image;
				if (image.HasPalette == false) {
					_ColorType = ColorType.FullColor;
				}
			}

			public void MergeImage(FreeImageBitmap bmp) {
				if (_Height == 0) {
					PasteFirstImage(bmp);
					return;
				}
				if (_ColorType == ColorType.FullColor) {
					PasteIntoFullColor(bmp);
					return;
				}
				if (bmp.HasPalette == false) {
					ExpandColorSpaceAndPaste(bmp);
					return;
				}
				var p = bmp.Palette.ToArray();
				if (p.SequenceEqual(_Colors)) {
					PasteAndIncrementHeight(bmp);
					return;
				}
				switch (_ColorType) {
					case ColorType.BlackAndWhite:
						if (p[0].Color == Color.White
							&& p[1].Color == Color.Black
							&& bmp.Invert()) {
							PasteAndIncrementHeight(bmp);
							return;
						}
						break;
					case ColorType.WhiteAndBlack:
						if (p[0].Color == Color.Black
							&& p[1].Color == Color.White
							&& bmp.Invert()) {
							PasteAndIncrementHeight(bmp);
							return;
						}
						break;
				}
				TryExpandColorDepth(_Image);
				TryExpandColorDepth(bmp);
				PasteAndIncrementHeight(bmp);
				_Colors = null;
				_ColorType = ColorType.FullColor;
			}

			public bool TryReduceColor() {
				if (_ColorType != ColorType.FullColor) {
					return false;
				}
				var c = _Image.UniqueColors;
				if (c > 256) {
					return false;
				}
				if (c < 3) {
					if (_Image.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_01_BPP)) {
						_ColorType = ColorType.BlackAndWhite;
						return true;
					}
					return false;
				}
				if (_Image.ConvertColorDepth(c < 17 ? FREE_IMAGE_COLOR_DEPTH.FICD_04_BPP : FREE_IMAGE_COLOR_DEPTH.FICD_08_BPP)) {
					_ColorType = ColorType.IndexedColor;
					return true;
				}
				return false;
			}

			void PasteFirstImage(FreeImageBitmap bmp) {
				if (bmp.HasPalette) {
					_Colors = new RGBQUAD[bmp.ColorsUsed];
					_Image.Palette.AsArray = bmp.Palette.AsArray;
					bmp.Palette.CopyTo(_Colors, 0);
					_ColorType = GetWhiteBlackType(_Colors);
				}
				_Image.Paste(bmp, 0, 0, Int32.MaxValue);
				_Height = bmp.Height;
			}

			static ColorType GetWhiteBlackType(RGBQUAD[] colors) {
				var c0 = colors[0].Color;
				var c1 = colors[1].Color;
				return c0 == Color.Black && c1 == Color.White ? ColorType.BlackAndWhite
					: c0 == Color.White && c1 == Color.Black ? ColorType.WhiteAndBlack
					: ColorType.IndexedColor;
			}

			void PasteIntoFullColor(FreeImageBitmap bmp) {
				if (bmp.HasPalette) {
					TryExpandColorDepth(bmp);
				}
				PasteAndIncrementHeight(bmp);
			}

			static void TryExpandColorDepth(FreeImageBitmap bmp) {
				if (bmp.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_24_BPP) == false) {
					throw new InvalidOperationException("合并图片失败：无法转换图片颜色");
				}
			}

			void PasteAndIncrementHeight(FreeImageBitmap bmp) {
				if (_Image.Paste(bmp, 0, _Height, Int32.MaxValue) == false) {
					throw new InvalidOperationException("合并图片失败：粘贴操作出错");
				}
				_Height += bmp.Height;
			}

			void ExpandColorSpaceAndPaste(FreeImageBitmap bmp) {
				TryExpandColorDepth(_Image);
				PasteIntoFullColor(bmp);
			}
		}

		enum ColorType
		{
			Undefined,
			BlackAndWhite,
			WhiteAndBlack,
			IndexedColor,
			FullColor,
		}

		sealed class PdfPageImageProcessor : PdfContentStreamProcessor
		{
			readonly List<ImageDisposition> _posList;
			readonly List<ImageInfo> _infoList;

			public PdfPageImageProcessor(List<ImageDisposition> posList, List<ImageInfo> infoList) {
				PopulateOperators();
				_posList = posList;
				_infoList = infoList;
			}
			protected override void InvokeOperator(PdfLiteral oper, List<PdfObject> operands) {
				base.InvokeOperator(oper, operands);
				switch (oper.ToString()) {
					case "Do":
						var xobjects = Resource.GetAsDict(PdfName.XOBJECT);
						var r = xobjects.GetAsIndirectObject(operands[0] as PdfName);
						var info = _infoList.Find(
							i => i.InlineImage.PdfRef != null
								&& i.InlineImage.PdfRef.Number == r.Number
								&& i.InlineImage.PdfRef.Generation == r.Generation);
						if (info != null) {
							info.ReferenceCount++;
							_posList.Add(new ImageDisposition(CurrentGraphicState.TransMatrix, info));
						}
						else {
							Trace.WriteLine(String.Concat("Image ", r, " not found."));
						}
						break;
					case "BI":
						info = new ImageInfo(new PdfImageData(operands[0] as PdfDictionary, ((PdfImageData)operands[0]).RawBytes));
						info.ReferenceCount++;
						_infoList.Add(info);
						_posList.Add(new ImageDisposition(CurrentGraphicState.TransMatrix, info));
						break;
				}
			}
		}
	}
}
